-- 计时器Timer
-- Author: Colocasia
-- Date: 2019-07-02

local skynet = require 'skynet';
local class = require 'class';
local tool = require 'tool';
local log = require 'log';

---@class timer
local timer = class('timer');
local timers = {};


-- static function start --
function timer.add_timer(id, interval, loop, callback)
    local t = timer.new(interval*100, loop, callback, id);
    t:start();
    return t;
end

function timer.del_timer(id)
    local t = timer.get_timer(id);
    if t then
        t:remove();
    end
end

function timer.get_timer(id)
    return timers[id];
end

function timer.update_timer(id, time)
    local t = timer.get_timer(id);
    if not t or not t:running() then return end

    local steps = time * 100;
    local nt = t:step(steps)
	timers[id] = nt
end

function timer.del_all_timer()
    for k, v in pairs(timers) do
		v:remove()
	end
	timers = {}
end

function timer.debug()
    print("\n[timer]---------------------")
    local n = 0;
    for k, v in pairs(timers) do
        print(string.format("[timer]guid->%s|session->%s", k, v.session));
        log.dump(v, "timer");
        n = n + 1;
    end
    print("[timer]counrt->"..n);
    print("[timer]---------------------\n")
end
-- static function end --



-- member function start --
---构造函数
---@param interval number
---@param loop boolean
---@param callback function
---@param id string
function timer:ctor(interval, loop, callback, id)
    assert(interval and type(interval) == 'number');
    assert(callback and type(callback) == 'function');

    local guid = id or tool:guid();
    timer.del_timer(guid);
    
	self.interval = interval > 0 and interval or 0;
	self.callback = callback;
	self.loop = loop;
    self.guid = guid;
    timers[guid] = self;
end

-- create a callback function to wrap the user call
local function create_timer_callback(self)
    return function()
        if not self.running then
            return 
        end

		xpcall(self.callback, function(err)
            log.error(err);
        end, self)
        
        -- callback执行过程中，running被设为false则不再调用start
        if self.loop and self.running then
            self:start()
        elseif self.loop and not self.running then
            -- do nothing
        else
            xpcall(self.remove, function(err)
                log.error(err);
            end, self);
		end
	end
end

function timer:start()
	self.running = true
	self.start_time = skynet.time()
	self.timer_callback = create_timer_callback(self)
	if self.interval <= 0 then
		self.timer_callback()
	else
		self.session = skynet.timeout(self.interval, self.timer_callback)
	end
end

-- !!! IMPORTANT !!!
-- you must call start before step.
-- the skynet cannot stop or change a timer's callback time.
-- so we have to make a new_timer to step the timer forward.
-- if the user want to step the timer for a period of time, he should conern this
function timer:step(time)
	assert(self.running, 'you must step a running timer.')
	self:stop();

	local old_timer = self:serial()
	local elapsed = math.floor((skynet.time() - self.start_time)*100)
	local new_timer = timer.new(old_timer.interval - elapsed - time, old_timer.loop, old_timer.callback, old_timer.guid)
	new_timer:start()
	return new_timer
end

function timer:stop()
	self.running = false;
    self.session = nil;
end

function timer:remove()
    self:stop();
    timers[self.guid] = nil;
end

function timer:serial()
	return {
		interval = self.interval,
		callback = self.callback,
		loop = self.loop,
		guid = self.guid,
	}
end

function timer:guid()
    return self.guid;
end

function timer:running()
    return self.running;
end

function timer:set_loop(loop)
    self.loop = loop;
end
-- member function end --

return timer;
